/**
 *  @file HttpMessage.cpp
 */
#include "HttpMessage.h"
#include "../../common/CppMemPool.h"
#include "../../convert/Convert.h"
#include "util/HttpUtility.h"
#include "HttpException.h"

using namespace cppflib::collections;
using namespace cppflib::net::http::message;

namespace cppflib
{

namespace net
{

namespace http
{

/**
 *   @return -1 if not preset
 *   @return index of header in the list (zero based)
 */
PRIVATE int __HeaderExists(NameValueList & headers, const CFString& name, CFString ** value)
{
   for (int i = 0 ; i < static_cast<int>(headers.GetCount()); ++i) {
      CFString *pKey = headers.GetKey(i);
      if (pKey->CompareNoCase(name) == 0) {  // header field names are case insensitive
         if (value)
            *value = headers.GetVal(i);
         return i;
      }
   }

   return -1;
}

HttpMessage::HttpMessage(void)
{
   this->pHeaders = NULL;
   this->pBody = NULL;
   this->httpVersion = http::HTTP_VER_1_1; // use 1.1 by default
}

HttpMessage::~HttpMessage(void)
{
   if (pHeaders)
      cppmempool::Release(pHeaders);

   if (pBody)
      cppmempool::Release(pBody);
}

void HttpMessage::SetHttpVersion(pcwstr_t ver)
{
   this->httpVersion = ver;
   if (httpVersion.Compare(http::HTTP_VER_1_0) != 0 && httpVersion.Compare(http::HTTP_VER_1_1) != 0)
      throw HttpException(_S("Invalid HTTP version"));
}

/**
 *  Add extra headers. Colon isn't required for 'name'
 *
 *  @param [in] isReplace -- true: old one is replaced if already exists
 *                           false: val is appended to current header value
 */
void HttpMessage::SetHeader(pcwstr_t name, pcwstr_t val, bool isReplace)
{
   if (!pHeaders) {
      pHeaders = new NameValueList();
      cppmempool::Retain(pHeaders);
   }

   CFString tempName(name);
   tempName.Trim(_S(" \t:"));
   CFString *pVal = NULL;

   int index = __HeaderExists(*pHeaders, tempName, &pVal);
   if (index >= 0) {
      if (isReplace) {
         pVal = new CFString(val);
         pVal->Trim(_S(" \t"));
         pHeaders->Set(index, new CFString(tempName), pVal); // replace old one
      }
      else {
         pVal->AppendFormat(_S(", %s"), val);   // append
      }
   }
   else {
      pVal = new CFString(val);
      pVal->Trim(_S(" \t"));
      pHeaders->AddTail(new CFString(tempName), pVal);    // add a new one
   }

}

/**
 *  Set body
 */
void HttpMessage::SetHttpBody(IHttpBody *pHttpBody)
{
   cppmempool::Retain(pHttpBody);
   if (pBody)
      cppmempool::Release(pBody);

   pBody = pHttpBody;
}

/**
 *  Get HTTP version (1.0 or 1.1)
 */
const CFString& HttpMessage::GetHttpVersion(void)
{
   return this->httpVersion;
}

/**
 *  Get number of extra headers
 */
u32_t HttpMessage::GetHeaderCount(void)
{
   return pHeaders ? pHeaders->GetCount() : 0;
}

/**
 *  Get an extra header name and value by index
 */
bool HttpMessage::GetHeader(int index, CFString*& name, CFString*& value)
{
   if (pHeaders) {
      pHeaders->Get(index, name, value);
      return true;
   }

   return false;
}

/**
 *  Get value by header name
 */
bool HttpMessage::GetHeaderValue(const CFString& name, CFString*& value)
{
   value = NULL;

   if (pHeaders) {
      CFString *pVal = NULL;
      if (__HeaderExists(*pHeaders, name, &pVal) >= 0) {
         value = pVal;
         return true;
      }
   }

   return false;
}

/**
 *  Test if a header exists
 */
bool HttpMessage::HeaderExists(const CFString& name)
{
   if (pHeaders) {
      return (__HeaderExists(*pHeaders, name, NULL) >= 0);
   }

   return false;
}

/**
 *  Test if a header and value pair exists
 */
bool HttpMessage::HeaderValueExists(const CFString& name, const CFString &value)
{
   if (pHeaders) {
      CFString *pVal;
      if (__HeaderExists(*pHeaders, name, &pVal) >= 0)
         return (pVal->FindNoCase(value) >= 0);
   }

   return false;
}

/**
 *  Whether a body is present
 */
bool HttpMessage::HasHttpBody(void)
{
   return (this->pBody != NULL);
}

/**
 *  Get body
 */
IHttpBody * HttpMessage::GetHttpBody(void)
{
   return this->pBody;
}

/**
 *  Test whether 
 *    Expect: 100-continue
 *  exists
 */
bool HttpMessage::ExpectContinueExists()
{
   return HeaderValueExists(CFString(_S("Expect")), CFString(_S("100-continue")));
}

/**
 *  Test whether 
 *    Transfer-Encoding: chunked
 *  exists
 */
bool HttpMessage::ChunkTransferEncodingExists()
{
   return HeaderValueExists(CFString(_S("Transfer-Encoding")), CFString(_S("chunked")));
}

/**
 *  Test whether 
 *    Content-Encoding: gzip    or    Content-Encoding: deflate
 *  exists
 */
bool HttpMessage::CompressedContentExists()
{
   return HeaderValueExists(CFString(_S("Content-Encoding")), CFString(_S("gzip"))) ||
          HeaderValueExists(CFString(_S("Content-Encoding")), CFString(_S("deflate")));
}

/**
 *  Test if this message is version 1.1
 */
bool HttpMessage::IsVersion1Point1()
{
   return (httpVersion.Compare(http::HTTP_VER_1_1) == 0);
}

/**
 *  @return true if the HTTP request method can have a body
 */
bool HttpMessage::CanRequestHaveBody(httpmethod_t method)
{
   return (method == http::POST || 
           method == http::PUT || 
           method == http::OPTIONS);
}

/**
 *  @return true if the HTTP response can have a body
 */
bool HttpMessage::CanResponseHaveBody(httpmethod_t method)
{
   return (method == http::GET || 
           method == http::POST || 
           method == http::PUT || 
           method == http::OPTIONS || 
           method == http::TRACE);
}

/**
 *  @return true if the response code will have no body
 */
bool HttpMessage::ResponseCodeHaveNoBody(int statusCode)
{
   return (statusCode == static_cast<int>(http::ST_CONTINUE) || 
           statusCode == static_cast<int>(http::ST_NOTMODIFIED));
}

} // end of namespace http

} // end of namespace net

} // end of namespace cppflib
